package com.cory.utils.security;

import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.SecureRandom;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import com.cory.utils.lang.ExceptionUtils;

/**
 * 基础加密组件
 */
public abstract class Coder {
	public static final String KEY_MD5 = "MD5";
	
    /**
     * SHA算法可选以下多种算法
     * 
     * <pre>
     * SHA
     * SHA-1 
     * SHA-256
     * SHA-384
     * SHA-512
     * </pre>
     */
	public static final String KEY_SHA = "SHA-1";

    /**
     * MAC算法可选以下多种算法
     * 
     * <pre>
     * HmacMD5 
     * HmacSHA1 
     * HmacSHA256 
     * HmacSHA384 
     * HmacSHA512
     * </pre>
     */
	public static final String KEY_MAC = "HmacMD5";
	
    /**
     * salt长度
     */
	public static final int DEFAULT_SALT_SIZE = 8;
	
    /**
     * HMAC密钥长度, RFC2401建议最少长度为160位(20字节).
     */
	public static final int DEFAULT_HMAC_KEYSIZE = 160; //RFC2401
	
    /**
     * 初始化Salt
     * 
     * @return byte[]
     */
	public static byte[] initSalt() {
		return initSalt(DEFAULT_SALT_SIZE);
	}
	
    /**
     * 初始化Salt
     * 
     * @return byte[]
     */
	public static byte[] initSalt(int size) {
		byte[] bytes = new byte[size];
		new SecureRandom().nextBytes(bytes);
		
		return bytes;
	}

    /**
     * MD5加密
     * 
     * @param data
     * @return byte[]
     */
	public static byte[] md5(byte[] data) {
		try {
			MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
			md5.update(data);
	
			return md5.digest();
		} catch(GeneralSecurityException e) {
			throw ExceptionUtils.unchecked(e);
		}
	}
	
    /**
     * MD5加密
     * 
     * @param data
     * @param salt
     * @param iterations
     * @return byte[]
     */
	public static byte[] md5(byte[] data, byte[] salt, int iterations) {
		try {
			MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
			
			if (salt != null) {
				md5.update(salt);
			}
			
			byte[] result = md5.digest(data);
	
			for (int i = 1; i < iterations; i++) {
				md5.reset();
				result = md5.digest(result);
			}
			return result;
		} catch(GeneralSecurityException e) {
			throw ExceptionUtils.unchecked(e);
		}
	}
	
    /**
     * 对文件进行MD5加密
     * 
     * @param input
     * @return
     * @throws IOException byte[]
     */
	public static byte[] md5(InputStream input) throws IOException {
		try {
			MessageDigest messageDigest = MessageDigest.getInstance(KEY_MD5);
			int bufferLength = 8 * 1024;
			byte[] buffer = new byte[bufferLength];
			int read = input.read(buffer, 0, bufferLength);

			while (read > -1) {
				messageDigest.update(buffer, 0, read);
				read = input.read(buffer, 0, bufferLength);
			}

			return messageDigest.digest();
		} catch (GeneralSecurityException e) {
			throw ExceptionUtils.unchecked(e);
		}
	}

    /**
     * SHA加密
     * 
     * @param data
     * @return byte[]
     */
	public static byte[] sha(byte[] data) {
		try{
			MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
			sha.update(data);
	
			return sha.digest();
		}catch(GeneralSecurityException e){
			throw ExceptionUtils.unchecked(e);
		}
	}
	
    /**
     * SHA加密
     * 
     * @param data
     * @param salt
     * @param iterations
     * @return byte[]
     */
	public static byte[] sha(byte[] data, byte[] salt, int iterations) {
		try {
			MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
			
			if (salt != null) {
				sha.update(salt);
			}
			
			byte[] result = sha.digest(data);
	
			for (int i = 1; i < iterations; i++) {
				sha.reset();
				result = sha.digest(result);
			}
			return result;
		} catch(GeneralSecurityException e) {
			throw ExceptionUtils.unchecked(e);
		}
	}
	
    /**
     * 对文件进行SHA加密
     * 
     * @param input
     * @return
     * @throws IOException byte[]
     */
	public static byte[] sha(InputStream input) throws IOException {
		try {
			MessageDigest messageDigest = MessageDigest.getInstance(KEY_SHA);
			int bufferLength = 8 * 1024;
			byte[] buffer = new byte[bufferLength];
			int read = input.read(buffer, 0, bufferLength);

			while (read > -1) {
				messageDigest.update(buffer, 0, read);
				read = input.read(buffer, 0, bufferLength);
			}

			return messageDigest.digest();
		} catch (GeneralSecurityException e) {
			throw ExceptionUtils.unchecked(e);
		}
	}

    /**
     * 初始化HMAC密钥
     * 
     * @return byte[]
     */
	public static byte[] initMacKey() {
		try{
			KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);
			keyGenerator.init(DEFAULT_HMAC_KEYSIZE);
	
			SecretKey secretKey = keyGenerator.generateKey();
			return secretKey.getEncoded();
		}catch(GeneralSecurityException e){
			throw ExceptionUtils.unchecked(e);
		}	
	}

    /**
     * HMAC加密
     * 
     * @param data
     * @param key
     * @return byte[]
     */
	public static byte[] hmac(byte[] data, byte[] key) {
		try{
			SecretKey secretKey = new SecretKeySpec(key, KEY_MAC);
			Mac mac = Mac.getInstance(secretKey.getAlgorithm());
			mac.init(secretKey);
	
			return mac.doFinal(data);
		}catch(GeneralSecurityException e){
			throw ExceptionUtils.unchecked(e);
		}	
	} 
	
}
